/*
 * SIT_ToolBar.c : toolstrip of buttons attached to the top of parent window.
 *
 * Written by T.Pierron, Apr 2010.
 */

#include "SIT_P.h"
#include "graphics.h"
#include <stdio.h>
#include <stdarg.h>

	TagList ToolbarClass[] = {
		{ SIT_SuperClass,   NULL,           ___, SIT_INT,  (ULONG) WidgetClass },
		{ SIT_ToolbarStyle, "toolbarStyle", C__, SIT_INT,  OFFSET(SIT_ToolBar, tb_Style) },
		{ SIT_ButtonList,   "buttonList",   CSG, SIT_PTR,  OFFSET(SIT_ToolBar, tb_Buttons) },
		{ SIT_LargeIcons,   "largeIcons",   C__, SIT_BOOL, OFFSET(SIT_ToolBar, tb_LargeIcons) },
		{ SIT_TagEnd }
	};

	static uint8_t nthbit[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};

/*
 * Obligatory rant about Win32 Toolbar:
 *
 * Win32 API would be soooo much better if Microsoft didn't always half-ass everything
 * it touches, which is not that unsurprising, seeing into which crapfest they trap
 * themselves into with such a drawn-out API. First problem: adding custom controls.
 * You'll need to reserve space between standard buttons. Here's the deal:
 * - Can't use TBSTYLE_SEP and iBitmap with the desired gap you want. First depending on
 *   Toolbar styles, it will draw a vertical separator, that you cannot even override using
 *   NM_CUSTOMDRAW (you can return CDRF_SKIPDEFAULT at every stage, vertical bar will still
 *   be drawn). It is possible to subclass control, trap WM_PAINT and erase separator box
 *   (been there, done that), but this is a PITA.
 * - I'm not sure how Rebar is going to help, as suggested by MSDN.
 * - The way it's done here, is to create a disabled button with a dummy fully transparent
 *   bitmap, setting its width using TB_SETBUTTONINFO message. Then we trap NM_CUSTOMDRAW
 *   in CDDS_PREPAINT phase and send CDRF_SKIPDEFAULT for the given ID. Supported since
 *   Win2000. Bonus: it is even seen by standard customize toolbar dialog.
 *
 * Also didn't find a way to auto-generate disabled bitmap, especially with 8bit mask.
 * If you set a disabled image list to the toolbar control, you'll have to supply
 * your own disabled mask for system icons.
 *
 * Also TB_GETMAXSIZE seems FUBAR with v5 comctl32.
 */
#define	tb ((SIT_ToolBar)w)
static int SIT_MeasureToolbar(SIT_Widget w, APTR cd, APTR unused)
{
	SIT_ToolButton * btn  = tb->tb_Buttons;
	TBBUTTONINFO     tbi  = {.cbSize = sizeof tbi, .dwMask = TBIF_SIZE};
	SIZE *           sz   = cd;
	int              vert = tb->tb_Style & SITV_Vertical;
	int              spc  = tb->tb_Style & SITV_ToolBarButtons ? 4 : 2;
	int              cid, nb, maxc;

	if (btn == NULL) return 0;

	for (cid = TB_CUSTOMCTRL, nb = 0, maxc = 0; btn->id; btn ++, nb ++)
	{
		if ((btn->flags & 7) != SITTB_Child) continue;
		SIT_Widget c = btn->child;
		RECT rect = {};
		SIZE pref = {};
		if (c->sw_OptimalBox.cx >= 0 && c->sw_OptimalBox.cy >= 0) { cid ++; continue; }

		c->sw_OptimalWidth(c, &pref, NULL);
		c->sw_MinBox = pref;
		SendMessage(tb->tb_Handle, TB_GETITEMRECT, nb, (LPARAM) &rect);
		if (maxc < pref.cy)
			maxc = pref.cy;
		c->sw_Fixed.x = rect.left + c->sw_Margins[0];
		if ((tb->tb_Style & SITV_NoDivider) == 0) c->sw_Fixed.y --;
		tbi.cx = pref.cx + c->sw_Margins[0] + c->sw_Margins[2];
		SendMessage(tb->tb_Handle, TB_SETBUTTONINFO, cid, (LPARAM) &tbi); cid ++;
	}

	SendMessage(tb->tb_Handle, TB_GETMAXSIZE, 0, (LPARAM) sz);
	if (sz->cy < maxc)
		sz->cy = maxc;
	if (vert) sz->cx += spc, w->sw_MinBox.cx = sz->cx;
	else      sz->cy += spc, w->sw_MinBox.cy = sz->cy;
//	w->sw_OptimalBox = w->sw_CurrentBox = sz;

//	fprintf(stderr, "toolbar sz = %ldx%ldx\n", sz.cx, sz.cy);

	for (btn = tb->tb_Buttons; btn->id; btn ++)
	{
		if ((btn->flags & 7) != SITTB_Child || ! btn->child) continue;
		SIT_Widget c = btn->child;
		c->sw_Box.x = c->sw_Fixed.x;
		c->sw_Fixed.y = c->sw_Box.y = (sz->cy - c->sw_MinBox.cy) / 2 - 1;
		c->sw_Box.width  = c->sw_Box.x + c->sw_MinBox.cx;
		c->sw_Box.height = c->sw_Box.y + c->sw_MinBox.cy;
		fprintf(stderr, "child '%S' = %dx%d\n", c->sw_Name, c->sw_Box.x, c->sw_Box.y);
	}
	return 0;
}
#undef tb

typedef struct
{
	BITMAPINFOHEADER bmiHeader;
	RGBQUAD          bmiColors[2];
}	BITMAPINFO2;

void ApplyMask(DATA8 dest, int w, int h, DATA8 src, int x, int y, ULONG color)
{
	int   i, j;
	DATA8 d, s, c = (DATA8) &color;

	for (j = y, d = dest + w * 4 * y, s = src; j < h; j ++, s += w)
	{
		for (i = 0; i < w; i ++, d += 4)
		{
			if (i < x) continue;
			if (s[i-x] > 128)
				d[0] = c[0], d[1] = c[1], d[2] = c[2], d[3] = 255;
			/* Less sharp with anti-aliasing :-/ */
/*			else if (s[i] > 0)
				d[0] = (c[0] * s[i] + d[0] * (255 - s[i])) / 255,
				d[1] = (c[1] * s[i] + d[1] * (255 - s[i])) / 255,
				d[2] = (c[2] * s[i] + d[2] * (255 - s[i])) / 255,
				d[3] = 255; */
		}
	}
}

static void SIT_GrayedImage(HIMAGELIST hlist, HIMAGELIST disa, int nth)
{
	IMAGEINFO info;
	BITMAPINFO2 bhdr;
	HBITMAP bmp, old;
	DATA8 bits, mbits, p, s;
	HDC hex = CreateDC(L"DISPLAY", NULL, NULL, NULL);
	HDC hdc = CreateCompatibleDC(hex);
	int i, j, h, stride;

	DeleteDC(hex);
	pImageList_GetImageInfo(hlist, nth, &info);

	#define	hdr   bhdr.bmiHeader
	h = info.rcImage.bottom - info.rcImage.top;
	memset(&hdr, 0, sizeof hdr);
	memcpy(bhdr.bmiColors, "\0\0\0\0\xff\xff\xff", 8);
	hdr.biSize = sizeof hdr;
	hdr.biWidth = info.rcImage.right - info.rcImage.left;
	hdr.biHeight = - h;
	hdr.biPlanes = 1;
	hdr.biBitCount = 32;
	hdr.biCompression = BI_RGB;

	bmp = CreateDIBSection(hdc, (APTR) &bhdr, DIB_RGB_COLORS, (LPVOID) &bits, NULL, 0);
	old = SelectObject(hdc, bmp);
	pImageList_Draw(hlist, nth, hdc, 0, 0, ILD_NORMAL);
	SelectObject(hdc, old);

	/* Check whether or not there is something meaningful in alpha channel */
	for (p = bits, i = stride = hdr.biWidth * h, mbits = alloca(i);
	     i > 0 && p[3] == bits[3]; i --, p += 4);

	if (i == 0)
	{
		/* Sigh. Use image mask data */
		DATA8 d;
		HBITMAP mask;
		hdr.biBitCount = 1;
		hdr.biClrUsed = 0;
		mask = CreateDIBSection(hdc, (APTR) &bhdr, DIB_RGB_COLORS, (LPVOID) &p, NULL, 0);
		old = SelectObject(hdc, mask);
		stride = ((hdr.biWidth*hdr.biBitCount/8+3) & ~3);
		memset(p, 0xff, stride * h);
		pImageList_Draw(hlist, nth, hdc, 0, 0, ILD_MASK);
		SelectObject(hdc, old);
		/* Convert 1 to 8bit */
		for (s = p, d = mbits, j = 0; j < h; j ++, s += stride)
			for (i = 0; i < hdr.biWidth; i ++)
				*d++ = s[i>>3] & nthbit[i&7] ? 0 : 255;
		DeleteObject(mask);
	}
	else for (i = stride, p = mbits, s = bits+3; i > 0; i --, *p++ = *s, s += 4);
	DeleteDC(hdc);

	/* What a pain in the ass */
	memset(bits, 0, hdr.biWidth * h * 4);
	ApplyMask(bits, hdr.biWidth, h, mbits, 1, 1, GetSysColor(COLOR_HIGHLIGHTTEXT));
	ApplyMask(bits, hdr.biWidth, h, mbits, 0, 0, GetSysColor(COLOR_3DSHADOW));
//	if (! has_syslink) GFX_FlattenImage();
	if (nth < pImageList_GetImageCount(disa))
		pImageList_Replace(disa, nth, bmp, NULL);
	else
		pImageList_Add(disa, bmp, NULL);
	DeleteObject(bmp);
}

HBITMAP SIT_GrayedImageAlpha(Image img, DATA8 dest)
{
	if (img == NULL || img->format < GFX_FormatRGB) return NULL;

	BITMAPINFO bhdr;
	DATA8      p, src;
	HBITMAP    bmp = 0;
	HDC        hex = CreateDC(L"DISPLAY", NULL, NULL, NULL);
	HDC        hdc = CreateCompatibleDC(hex);
	int        i, j, step = img->bpp>>3;

	DeleteDC(hex);

	memset(&hdr, 0, sizeof hdr);
	hdr.biSize = sizeof hdr;
	hdr.biWidth = img->width;
	hdr.biHeight = -img->height;
	hdr.biPlanes = 1;
	hdr.biBitCount = img->bpp;
	hdr.biCompression = BI_RGB;

	if (dest == NULL)
		bmp = CreateDIBSection(hdc, &bhdr, DIB_RGB_COLORS, (LPVOID) &dest, NULL, 0);
	DeleteDC(hdc);

	/* Grayscale and lighten the image */
	for (src = img->bitmap, p = dest, j = img->height; j > 0; j --, p += img->stride, src += img->stride)
	{
		DATA8 s, d;
		for (i = img->width, s = src, d = p; i > 0; i --, s += step, d += step)
		{
			/* Y = 0.3*R + 0.59*G + 0.11*B */
			int v = ((28*s[0]+151*s[1]+77*s[2]) >> 8);
			d[0] = d[1] = d[2] = (v > 255 ? 255 : v);
			if (step == 4) d[3] = s[3];
		}
	}
	if (! has_syslink && hdr.biBitCount == 32)
	{
		struct Image_t temp = *img;
		temp.bitmap = dest;
		GFX_FlattenImage(&temp, SYSCOL(GFX_ColorBgDialog));
	}
	return bmp;
	#undef	hdr
}

static void SIT_ToolBarSetList(SIT_ToolBar tb, int w, int h)
{
	if (! tb->tb_ImgList) {
		tb->tb_ImgList = pImageList_Create(w, h, ILC_COLOR32 | ILC_MASK, 16, 16);
		SendMessage(tb->tb_Handle, TB_SETIMAGELIST, 0, (LPARAM)tb->tb_ImgList);
	}

	if (! tb->tb_Disabled) {
		tb->tb_Disabled = pImageList_Create(w, h, ILC_COLOR32 | ILC_MASK, 16, 16);
		SendMessage(tb->tb_Handle, TB_SETDISABLEDIMAGELIST, 0, (LPARAM)tb->tb_Disabled);
	}
}

typedef struct InfoTip_t *    InfoTip;

struct InfoTip_t
{
	ListNode node;
	int      id;
	WCHAR    text[1];
};

static void SIT_ToolbarAddTip(SIT_ToolBar tb, SIT_ToolButton * btn, LPWSTR text, LPWSTR accel)
{
	if (text == NULL)
		UTF8ToUTF16(btn->tooltip, text);

	InfoTip tip;
	int     len = 0;

	if (text)  len  = wcslen(text);
	if (accel) len += wcslen(accel) + 3;

	tip = malloc(sizeof * tip + len * sizeof *text);
	tip->id = btn->id;
	ListAddTail(&tb->tb_Tips, &tip->node);
	if (text) wcscpy(tip->text, text);
	if (accel == NULL) return;
	if (tip->text[0]) wsprintf(wcschr(tip->text, 0), L" (%s)", accel);
	else wcscpy(tip->text, accel);
}

LPWSTR SIT_ToolbarGetTip(SIT_Widget w, int id)
{
	InfoTip t;

	for (t = HEAD(((SIT_ToolBar)w)->tb_Tips); t && t->id != id; NEXT(t));

	return t ? t->text : NULL;
}

static void SIT_ToolbarFreeTip(SIT_Widget w)
{
	ListHead * hdr = &((SIT_ToolBar)w)->tb_Tips;

	while (hdr->lh_Count > 0)
		free(ListRemHead(hdr));
}

static void SIT_SetToolButtons(SIT_ToolBar tb)
{
	SIT_ToolButton * btn;
	SIT_Widget       dialog;
	MENUITEMINFO     info = {.cbSize = sizeof info, .fMask = MIIM_FTYPE|MIIM_STRING};
	LPTBBUTTON       list, tbb;
	HWND             hwnd = tb->tb_Handle;
	HMENU            menu = NULL;
	int              nb, next, cid, master;
	int              spc = (tb->tb_Style&3) == SITV_ToolBarList ? 2 : 0;

	for (dialog = tb->super.sw_Parent; dialog && dialog->sw_Type != SIT_DIALOG;
	     dialog = dialog->sw_Parent);

	if (dialog) menu = ((SIT_Dialog)dialog)->sd_MenuBar;

	for (nb = 0, btn = tb->tb_Buttons; btn->id; btn ++, nb ++);
	list = alloca(nb * sizeof *list);
	memset(list, 0, nb * sizeof *list);

	for (btn = tb->tb_Buttons, tbb = list, master = next = 0, cid = TB_CUSTOMCTRL; btn->id; btn ++, tbb ++)
	{
		LPWSTR text;
		ULONG  btnid = (ULONG) btn->icon;
		int    style = 0;
		int    state = TBSTATE_ENABLED;
		if (btnid > 64)
		{
			STRPTR  path = btn->icon;
			Bool    altd = strncasecmp(path, "alt:", 4) == 0;
			HBITMAP disa = NULL;
			Image   img;
			if (altd) path += 4;
			img = (btn->flags & SITTB_GfxImage) ? (Image) path : GFX_LoadImage(path);
			if (! img) continue;
			if (! altd) disa = SIT_GrayedImageAlpha(img, NULL);
			altd = img->bpp == 32;
			if (! has_syslink) GFX_FlattenImage(img, SYSCOL(GFX_ColorBgDialog));
			if (! tb->tb_ImgList)
				SIT_ToolBarSetList(tb, img->width, img->height);
			btnid = pImageList_Add(tb->tb_ImgList, img->hbitmap, NULL);
			/* Allow two dithering methods */
			if (disa) pImageList_Add(tb->tb_Disabled, disa, NULL), DeleteObject(disa);
			else SIT_GrayedImage(tb->tb_ImgList, tb->tb_Disabled, btnid);
			if (btn->flags & SITTB_GfxImage)
				GFX_FreeImage(img);
			if (tb->tb_Style & SITV_SyncWithMenu)
				SIT_AddMenuIcon(dialog, btn->id, tb->tb_ImgList, altd ? (HIMAGELIST)-1 : tb->tb_Disabled, btnid, NULL);
		}
		else if (btnid > 0)
		{
			static uint8_t presets[] = {
				IDB_HIST_SMALL_COLOR, IDB_HIST_LARGE_COLOR,
				IDB_STD_SMALL_COLOR,  IDB_STD_LARGE_COLOR,
				IDB_VIEW_SMALL_COLOR, IDB_VIEW_LARGE_COLOR
			};
			int set = ((btnid & 3) << 1) | tb->tb_LargeIcons;

			if (tb->tb_Index[set] == 0)
			{
				int i, n = tb->tb_LargeIcons ? 24 : 16;

				if (! tb->tb_ImgList)
					SIT_ToolBarSetList(tb, n, n);

				/* Load preset toolbar icons */
				tb->tb_Index[set] = i = SendMessage(hwnd, TB_LOADIMAGES, presets[set],
					(LPARAM)HINST_COMMCTRL);
				for (n = pImageList_GetImageCount(tb->tb_ImgList); i < n; i ++)
					SIT_GrayedImage(tb->tb_ImgList, tb->tb_Disabled, i);
			}
			btnid = tb->tb_Index[set] + (btnid >> 2) - 1;
			tbb->dwData |= (set+1) << 16;
			if (tb->tb_Style & SITV_SyncWithMenu)
				SIT_AddMenuIcon(dialog, btn->id, tb->tb_ImgList, tb->tb_Disabled, btnid, NULL);
		}
		else btnid = I_IMAGENONE;
		btn->reserved = btn->id;
		switch (btn->flags & 7) {
		case SITTB_DropDown: style |= BTNS_WHOLEDROPDOWN; break;
		case SITTB_BtnPopup: style |= BTNS_DROPDOWN; break;
		case SITTB_Child:
			state &= ~TBSTATE_ENABLED;
			btn->reserved = cid ++;
			tb->tb_HasCustCtrl = True; // We'll need to catch NM_CUSTOMDRAW
			if ((tb->tb_Style & 3) == SITV_ToolBarList) style |= BTNS_SHOWTEXT;
		}
		if (btn->flags > 0xff && !next)  master = (tbb - list) + 1; tbb->dwData |= master;
		if (btn->id < 0)                 style |= BTNS_SEP;
		if (btn->flags & SITTB_Disabled) state &= ~TBSTATE_ENABLED;
		if (btn->flags & SITTB_Hidden)   state |= TBSTATE_HIDDEN;
		if (btn->flags & SITTB_Checked)  state |= TBSTATE_CHECKED;
		if (btn->id == next)             style |= BTNS_CHECK|BTNS_GROUP;
		if (btn->flags & 0x04)           style |= BTNS_CHECK;
		if (btn->flags > 0xfff)          style |= BTNS_GROUP, next = btn->flags >> 12;
		else next = master = 0;
		tbb->idCommand = btn->reserved;
		tbb->iBitmap   = btnid;
		tbb->fsState   = state;
		tbb->fsStyle   = style;
		// Usual half-ass job: text is outside box without icon using TBSTYLE_LIST style.
		// (TB_SETBUTTONINFO with TBIF_SIZE cannot adjust text. Do it quick'n dirty: add space before :-/
		style = (btnid == I_IMAGENONE ? spc : 0);
		if (btn->text == SITTB_MenuName)
		{
			/* Quote tool button label directly from menu */
			if (! menu) continue;
			info.dwTypeData = NULL;
			if (GetMenuItemInfo(menu, btn->id, FALSE, &info))
			{
				int    len  = ++ info.cch + style;
				LPWSTR text = alloca(len * sizeof *text);

				info.dwTypeData = text + style;
				tbb->iString = (INT_PTR) text;
				memcpy(text, L"  ", style * sizeof *text); /* Add left margin. Sigh */
				GetMenuItemInfo(menu, btn->id, FALSE, &info);
				text = wcschr(text, '\t');
				/* Add menu shortcut as tooltip */
				if (text && (btn->flags & SITTB_AutoTips))
					SIT_ToolbarAddTip(tb, btn, NULL, text+1), *text = 0;
				tbb->fsStyle |= BTNS_SHOWTEXT;
				text = wcsstr(info.dwTypeData, L"...");
				if (text) *text = 0;
			}
		}
		else
		{
			LPWSTR accel = NULL;
			info.dwTypeData = NULL;
			if (IsDef(btn->text))
			{
				tbb->fsStyle |= BTNS_SHOWTEXT;
				int len = MultiByteToWideChar(CP_UTF8, 0, btn->text, -1, NULL, 0) + style;
				text = alloca(len * sizeof *text);
				MultiByteToWideChar(CP_UTF8, 0, btn->text, -1, text + style, len);
				memcpy(text, L"  ", style * sizeof *text);
				tbb->iString = (INT_PTR) text;
			}
			if ((btn->flags & SITTB_AutoTips) || btn->tooltip)
			{
				if (GetMenuItemInfo(menu, btn->id, FALSE, &info))
				{
					int    len  = ++ info.cch;
					LPWSTR text = alloca(len * sizeof *text);

					info.dwTypeData = text;
					GetMenuItemInfo(menu, btn->id, FALSE, &info);
					text = wcschr(text, '\t');
					if (text) *text = 0, accel = text + 1;
					text = wcsstr(info.dwTypeData, L"...");
					if (text) *text = 0;
				}
				else if (! btn->tooltip)
				{
					continue;
				}
				SIT_ToolbarAddTip(tb, btn, btn->tooltip ? NULL : info.dwTypeData, accel);
			}
		}
	}
	/* If toolbar contain only text: adjust margins */
	SendMessage(hwnd, TB_ADDBUTTONS, nb, (LPARAM) list);

	for (btn = tb->tb_Buttons, nb = 0, cid = TB_CUSTOMCTRL; btn->id; btn ++, nb ++)
	{
		if ((btn->flags & 7) != SITTB_Child) continue;
		/*
		 * Hack from MSDN: even if we reparent the control, it will still send
		 * notifications to the parent window passed to CreateWindow(). That way
		 * we won't have to subclass toolbar to catch those notifications.
		 */
		SIT_Widget w = btn->child;
		if (w)
		{
			SetParent(w->sw_Handle, hwnd);
			w->sw_Flags |= SITF_FixedX | SITF_FixedY;
			memset(w->sw_Attachment, 0, sizeof w->sw_Attachment);
		}
	}
}

#define	tool  ((SIT_ToolBar)w)
#define	tag   ((TagList *)cd)

static int SIT_SetToolbarValues(SIT_Widget w, APTR cd, APTR ud)
{
	if (tag->tl_TagID == SIT_ButtonList && w->sw_Handle)
	{
		tool->tb_Buttons = ((SIT_Variant *)ud)->pointer;
		SIT_SetToolButtons(tool);
		return 0;
	}
	else return SIT_SetWidgetValue(w, cd, ud);
}

static int SIT_FinalizeToolbar(SIT_Widget w, APTR cd, APTR ud)
{
	if (tool->tb_ImgList)  pImageList_Destroy(tool->tb_ImgList);
	if (tool->tb_Disabled) pImageList_Destroy(tool->tb_Disabled);
	if (tool->tb_Style & SITV_SyncWithMenu)
	{
		SIT_Widget d;
		for (d = w->sw_Parent; d && d->sw_Type != SIT_DIALOG; d = d->sw_Parent);
		if (d) ListRemove(&((SIT_Dialog)d)->sd_SyncTB, &tool->tb_Sync);
	}
	SIT_ToolbarFreeTip(w);
	return 1;
}

#undef	tag
#undef	tool

/* Create and initialize a button, check box or radio button */
BOOL SIT_CreateToolbar(SIT_Widget w, va_list args)
{
	static SIT_Attach def[3] = {
		{0, SITV_AttachForm, -32000}, // Will be clipped by layout manager
		{0, SITV_AttachForm, -32000},
		{0, SITV_AttachForm, -32000}
	};
	SIT_ToolBar tb = (APTR) w;
	int         style = WS_CHILD | TBSTYLE_TOOLTIPS;
	HWND        hwnd;

	/* Default attachment: top of window */
	memcpy(w->sw_Attachment, def, sizeof def);

	w->sw_OptimalWidth = SIT_MeasureToolbar;
	w->sw_Finalize     = SIT_FinalizeToolbar;
	w->sw_SetValue     = SIT_SetToolbarValues;

	SIT_ParseTags(w, args, w->sw_Class = ToolbarClass);

	switch (tb->tb_Style & 3) {
	case SITV_ToolBarFlat: style |= TBSTYLE_FLAT; break;
	case SITV_ToolBarList: style |= TBSTYLE_FLAT | TBSTYLE_LIST;
	}
	if (tb->tb_Style & SITV_SyncWithMenu)
	{
		SIT_Dialog d = (APTR) w;
		while (d->super.sw_Type != SIT_DIALOG) d = (APTR) d->super.sw_Parent;
		ListAddTail(&d->sd_SyncTB, &tb->tb_Sync);
	}
	if (IsVisible(w)) style |= WS_VISIBLE;
	if (HAS_TAB(w))   style |= WS_TABSTOP;
	if (tb->tb_Style & SITV_NoDivider) style |= CCS_NODIVIDER;
	if (tb->tb_Style & SITV_Vertical) {
		style |= CSS_VERT;
		if (memcmp(def, w->sw_Attachment, sizeof def) == 0)
			memcpy(w->sw_Attachment + 3, def, sizeof def[0]),
			memset(w->sw_Attachment + 2, 0, sizeof def[0]);
	}

	/*
	 * PITA as usual(TM): toolbar is higher than it should be. No way to resize it,
	 * so wrap it up in a window. We'll be able at least to place it anywhere.
	 */
	WCHAR clsName[48];
	SIT_RegisterClass(w, clsName, 0, IDC_ARROW, NULL);
	w->sw_Handle = hwnd = CreateWindowEx(0, clsName, w->sw_Name, style | WS_CLIPCHILDREN,
		0, 0, 10, 10, SIT_GetContainer(w), (HMENU) MAKE_ID(w), instance, NULL);

	SetProp(hwnd, sit_class, (HANDLE) w);

	hwnd = tb->tb_Handle = CreateWindowEx(0, TOOLBARCLASSNAME,
		w->sw_Name, style, 0, 0, 10, 10, w->sw_Handle, (HMENU) MAKE_ID(w), instance, NULL);
	if (hwnd == NULL) return FALSE;

	SendMessage(hwnd, CCM_SETVERSION, 5, 0);
	SendMessage(hwnd, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
	SendMessage(hwnd, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS);

	SIT_SetDefaultFont(w);
	if (tb->tb_Buttons) SIT_SetToolButtons(tb);

	/* Keep track of class inside callback */
	SetProp(hwnd, sit_class, (HANDLE) w);

	return TRUE;
}

#define	tool  ((SIT_ToolBar)w)

/* Allow user to set custom controls as tool buttons */
DLLIMP void SIT_AssignToolButtons(SIT_Widget w, SIT_ToolButton * buttons, ...)
{
	SIT_ToolButton * btn;
	va_list args;
	int id;

	va_start(args, buttons);
	while ((id = va_arg(args, int)))
	{
		for (btn = buttons; btn->id && btn->id != id; btn ++);
		if (btn->id)
		{
			btn->flags |= SITTB_Child;
			btn->child  = va_arg(args, APTR);
		}
		else va_arg(args, APTR);
	}
	va_end(args);

	tool->tb_Buttons = buttons;
	SIT_SetToolButtons(tool);
}

void SIT_ToolbarSyncWithMenu(SIT_Widget w, int id)
{
	TBBUTTONINFO tinf = {.cbSize = sizeof tinf, .dwMask = TBIF_STATE|TBIF_LPARAM};
	MENUITEMINFO minf = {.cbSize = sizeof minf, .fMask = MIIM_STATE};
	SIT_Widget d;
	for (d = w; d && d->sw_Type != SIT_DIALOG; d = d->sw_Parent);
	if (SendMessage(tool->tb_Handle, TB_GETBUTTONINFO, id, (LPARAM) &tinf) >= 0 &&
	    GetMenuItemInfo(((SIT_Dialog)d)->sd_MenuBar, id, FALSE, &minf))
	{
		int cur   = tinf.fsState & (TBSTATE_ENABLED | TBSTATE_CHECKED);
		int stena = minf.fState & MFS_GRAYED  ? 0 : TBSTATE_ENABLED;
		int stchk = minf.fState & MFS_CHECKED ? TBSTATE_CHECKED : 0;

		if ((cur ^ stena) & TBSTATE_ENABLED) SIT_EnableMenu(d, -id, !stena);
		if ((cur ^ stchk) & TBSTATE_CHECKED) SIT_CheckMenu(d, -id, !stchk, False);
	}
}

/* Modify a few attributes of toolbar buttons on the fly */
DLLIMP Bool SIT_SetToolButton(SIT_Widget w, int id, int flags, STRPTR text, Image img)
{
	TBBUTTONINFO info = {.cbSize = sizeof info, .dwMask = TBIF_STATE|TBIF_IMAGE|TBIF_STYLE|TBIF_LPARAM};

	Bool sync = tool->tb_Style & SITV_SyncWithMenu;
	if (id < 0) sync = False, id = -id;
	int nth = SendMessage(tool->tb_Handle, TB_GETBUTTONINFO, id, (LPARAM) &info);
	if (nth < 0) return False; /* Bad button id */

	if (img != DontChangePtr)
	{
		if (img)
		{
			if ((info.lParam >> 16) > 0)
			{
				/* Do not overwrite pre-defined bitmaps */
				info.lParam &= 0xffff;
				pImageList_Add(tool->tb_ImgList, img->hbitmap, NULL);
			}
			else pImageList_Replace(tool->tb_ImgList, info.iImage, img->hbitmap, NULL);
			if (! (flags & SITTB_AltGray))
			{
				HBITMAP disa = SIT_GrayedImageAlpha(img, NULL);
				pImageList_Replace(tool->tb_Disabled, info.iImage, disa, NULL);
				DeleteObject(disa);
			}
			else SIT_GrayedImage(tool->tb_ImgList, tool->tb_Disabled, info.iImage);
		}
		else info.iImage = I_IMAGENONE;
	}
	else info.dwMask &= ~TBIF_IMAGE;

	if (text != DontChangePtr)
	{
		if (text)
		{
			int len = MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);
			int spc = (tool->tb_Style&3) == SITV_ToolBarList && info.iImage == I_IMAGENONE ? 2 : 0;
			info.pszText = alloca((len+spc) * sizeof *info.pszText);

			MultiByteToWideChar(CP_UTF8, 0, text, -1, info.pszText+spc, len);
			memcpy(info.pszText, L"  ", spc * sizeof *info.pszText);
		}
		else info.pszText = L"";
		info.dwMask |= TBIF_TEXT;
	}

	int mask = TBSTATE_ENABLED | TBSTATE_HIDDEN | TBSTATE_CHECKED;
	int cur  = (flags & SITTB_Disabled ? 0 : TBSTATE_ENABLED) |
	           (flags & SITTB_Hidden   ? TBSTATE_HIDDEN : 0)  |
	           (flags & SITTB_Checked  ? TBSTATE_CHECKED : 0);

	/* Change only one flag */
	if (flags & 0x1000000)
	{
		/*if (flags & SITTB_Disabled)*/ cur ^= TBSTATE_ENABLED;
		info.fsState ^= TBSTATE_ENABLED;
		int flag = (mask ^ cur) & info.fsState; /* Flags that did not changed */
		if (flags & 1) cur |= flag;
		else cur = flag;
		/*if (flags & SITTB_Disabled)*/ cur ^= TBSTATE_ENABLED;
		info.fsState ^= TBSTATE_ENABLED;
	}

	/* Do not modify state if it hasn't changed (avoid flicker) */
	if (cur == (info.fsState & mask))
		info.dwMask &= ~TBIF_STATE;
	else
		info.fsState = cur;

	info.dwMask |= TBIF_BYINDEX;
	if ((info.lParam & 0xffff) > 0)
	{
		/* Auto uncheck button in group */
		TBBUTTONINFO sub = {.cbSize = sizeof sub, .dwMask = TBIF_BYINDEX|TBIF_STATE|TBIF_STYLE|TBIF_COMMAND};
		int i;
		for (i = (info.lParam&0xffff)-1; ; i ++)
		{
			if (i == nth) continue;
			if (SendMessage(tool->tb_Handle, TB_GETBUTTONINFO, i, (LPARAM) &sub) < 0 ||
			    (sub.fsStyle & BTNS_GROUP) == 0) break;
			if (sub.fsState & TBSTATE_CHECKED)
			{
				sub.fsState &= ~TBSTATE_CHECKED;
				sub.dwMask &= ~(TBIF_STYLE|TBIF_COMMAND);
				SendMessage(tool->tb_Handle, TB_SETBUTTONINFO, i, (LPARAM) &sub);
				if (sync) SIT_CheckMenu(w, - sub.idCommand, False, False);
				break;
			}
		}
	}
	Bool ret = SendMessage(tool->tb_Handle, TB_SETBUTTONINFO, nth, (LPARAM) &info);

	if (sync)
	{
		/* Sync SITTB_Checked and SITTB_Enabled with corresponding menu ID */
		SIT_Widget d;
		for (d = w->sw_Parent; d->sw_Type != SIT_DIALOG; d = d->sw_Parent);
		if ((cur ^ info.fsState) & SITTB_Disabled) SIT_EnableMenu(d, -id, cur ^ SITTB_Disabled);
		if ((cur ^ info.fsState) & SITTB_Checked)  SIT_CheckMenu(d, -id, cur & SITTB_Checked, False);
	}

	SIT_ToolButton * btn;

	/* Need to adjust child position */
	for (btn = tool->tb_Buttons, nth = 0; btn->id; btn ++, nth ++)
	{
		if ((btn->flags & 7) != SITTB_Child) continue;
		SIT_Widget c = btn->child;
		RECT rect = {};
		SendMessage(tool->tb_Handle, TB_GETITEMRECT, nth, (LPARAM) &rect);
		rect.left += c->sw_Margins[0];
		if (c->sw_Fixed.x != rect.left) SIT_SetValues(c, SIT_X, rect.left, NULL);
	}
	return ret;
}
#undef tool
